/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { HvigorNode } from '@ohos/hvigor';
import { OhosModuleContext, Target } from '@ohos/hvigor-ohos-plugin/src/plugin/context/plugin-context';
import { BaseAnalyzeResult, ExtensionContext } from './Interface';
import { TemplateMetadata, TemplateMetadataImpl } from './TemplateMetadataImpl';
import { Logger } from '../../utils';

/**
 * Implementation of the ExtensionContext interface
 *
 * This class provides a concrete implementation of the ExtensionContext interface
 * and serves as the shared data container for all extensions.
 * It maintains both
 * shared data (like analyze results and template data) and extension-specific data
 * through private storage.
 */
export class ExtensionContextImpl implements ExtensionContext {
  // Base context fields shared by all extensions
  node: HvigorNode;
  moduleContext: OhosModuleContext;
  currentTarget?: Target;
  moduleIgnored?: boolean;
  // scanFiles is exposed via getter/setter to support per-target scoping
  private moduleScanFilesMaster: string[] = [];

  // Private fields for storing shared data
  private templateMetadata: TemplateMetadata;

  /**
   * Per-target scoped state container. Keyed by target name. A special key '__global__'
   * is used when no currentTarget is active (initialization phase).
   */
  private perTargetState: Map<string, {
    scanFiles: string[];
    analyzeResults: Set<BaseAnalyzeResult>;
    currentView: BaseAnalyzeResult[];
    currentFilePath?: string;
    // HMRouter-specific but kept loosely typed for decoupling at framework layer
    routerMap: any[];
    generatedPaths: Map<string, string>;
  }> = new Map();

  // Accessors are provided for currentView/_currentFilePath to retain compatibility

  /**
   * Creates a new ExtensionContextImpl
   *
   * @param {HvigorNode} node - The Hvigor node for this context
   * @param {OhosModuleContext} moduleContext - The module context from Hvigor
   */
  constructor(node: HvigorNode, moduleContext: OhosModuleContext) {
    this.node = node;
    this.moduleContext = moduleContext;
    this.templateMetadata = new TemplateMetadataImpl();
  }

  /**
   * Helper: get active target key. When currentTarget is undefined, use '__global__'.
   */
  private getActiveTargetKey(): string {
    return this.currentTarget?.getTargetName() ?? '__global__';
  }

  /**
   * Helper: get or create per-target state by key
   */
  private getOrCreateState(key: string): {
    scanFiles: string[];
    analyzeResults: Set<BaseAnalyzeResult>;
    currentView: BaseAnalyzeResult[];
    currentFilePath?: string;
    routerMap: any[];
    generatedPaths: Map<string, string>;
  } {
    let state = this.perTargetState.get(key);
    if (!state) {
      state = {
        scanFiles: [],
        analyzeResults: new Set<BaseAnalyzeResult>(),
        currentView: [],
        currentFilePath: undefined,
        routerMap: [],
        generatedPaths: new Map<string, string>(),
      };
      this.perTargetState.set(key, state);
    }
    return state;
  }

  /**
   * 获取模块级主扫描文件列表（在应用目标范围之前使用）
   */
  public getModuleScanFiles(): string[] {
    return this.moduleScanFilesMaster;
  }

  /**
   * Get current file analysis results (read-only)
   *
   * @returns {ReadonlyArray<BaseAnalyzeResult>} Current file analysis results
   */
  get currentView(): ReadonlyArray<BaseAnalyzeResult> {
    const key = this.getActiveTargetKey();
    return this.getOrCreateState(key).currentView;
  }

  /**
   * Add one or more analyze results to the shared collection
   *
   * This method validates each result before adding it to ensure data integrity.
   *
   * @param {BaseAnalyzeResult | BaseAnalyzeResult[]} results - The result to add
   */
  addAnalyzeResults(results: BaseAnalyzeResult | BaseAnalyzeResult[]): void {
    const key = this.getActiveTargetKey();
    const state = this.getOrCreateState(key);

    if (Array.isArray(results)) {
      results.forEach((result) => {
        if (!this.validateAnalyzeResult(result)) {
          Logger.warn(this.node.getNodeName(), `Invalid analyze result: ${JSON.stringify(result)}`);
          return;
        }
        state.analyzeResults.add(result);
      });
    } else {
      if (!this.validateAnalyzeResult(results)) {
        Logger.warn(this.node.getNodeName(), `Invalid analyze result: ${JSON.stringify(results)}`);
        return;
      }
      state.analyzeResults.add(results);
    }

    // Automatically sync to currentView for current file results (per target)
    this.syncToCurrentView(results);
  }

  /**
   * Get all analysis results of the specified type
   *
   * @template T - Type parameter extending BaseAnalyzeResult
   * @returns {Set<T>} Set of analyze results cast to the specified type
   */
  getAnalyzeResults<T extends BaseAnalyzeResult = BaseAnalyzeResult>(): Set<T> {
    const key = this.getActiveTargetKey();
    return this.getOrCreateState(key).analyzeResults as Set<T>;
  }

  /**
   * Get template data for a specific component
   *
   * @param {string} componentName - The name of the component
   * @returns {Record<string, unknown> | undefined} The template data or undefined if none exists
   */
  getTemplateData(componentName: string): Record<string, unknown> | undefined {
    return this.templateMetadata.getTemplateData(componentName);
  }

  /**
   * Get the entire template data map
   *
   * @returns {Map<string, Record<string, unknown>>} Map of component names to template data
   */
  getTemplateDataMap(): Map<string, Record<string, unknown>> {
    return this.templateMetadata.templateDataMap;
  }

  /**
   * Add template data for a specific component
   *
   * @param {string} componentName - The name of the component
   * @param {Record<string, any>} data - The template data to add
   */
  addTemplateData(componentName: string, data: Record<string, any>): void {
    // Directly add the template data without validation will be performed later in validateTemplateData
    this.templateMetadata.addTemplateData(componentName, data);
  }

  /**
   * Validate that an analysis result has all required fields
   *
   * @param {BaseAnalyzeResult} result - The result to validate
   * @returns {boolean} True if the result is valid, false otherwise
   * @private
   */
  private validateAnalyzeResult(result: BaseAnalyzeResult): boolean {
    // Check for required fields: name, sourceFilePath, and annotation
    return result.name !== undefined && result.sourceFilePath !== undefined && result.annotation !== undefined;
  }

  /**
   * Sync results to currentView for current file only
   *
   * @param {BaseAnalyzeResult | BaseAnalyzeResult[]} results - The results to sync
   * @private
   */
  private syncToCurrentView(results: BaseAnalyzeResult | BaseAnalyzeResult[]): void {
    const key = this.getActiveTargetKey();
    const state = this.getOrCreateState(key);
    if (!state.currentFilePath) { return };

    const resultsArray = Array.isArray(results) ? results : [results];
    const currentFileResults = resultsArray.filter(
      result => result.sourceFilePath === state.currentFilePath
    );

    state.currentView.push(...currentFileResults);
  }

  /**
   * Set current file path and clear currentView (internal use only)
   *
   * @param {string} filePath - The path of the file currently being processed
   */
  setCurrentFilePath(filePath: string): void {
    const key = this.getActiveTargetKey();
    const state = this.getOrCreateState(key);
    state.currentFilePath = filePath;
    state.currentView = []; // Clear previous file results for this target
  }

  /**
   * Accessor for scanFiles to support per-target storage while keeping the original field name.
   */
  get scanFiles(): string[] {
    const key = this.getActiveTargetKey();
    if (key === '__global__') {
      return this.moduleScanFilesMaster;
    }
    return this.getOrCreateState(key).scanFiles;
  }

  set scanFiles(files: string[]) {
    const key = this.getActiveTargetKey();
    if (key === '__global__') {
      this.moduleScanFilesMaster = files;
    } else {
      this.getOrCreateState(key).scanFiles = files;
    }
  }

  /**
   * Backward-compatible accessors for private fields used by extensions via `as any`.
   */
  get analyzeResults(): Set<BaseAnalyzeResult> {
    const key = this.getActiveTargetKey();
    return this.getOrCreateState(key).analyzeResults;
  }

  set analyzeResults(v: Set<BaseAnalyzeResult>) {
    const key = this.getActiveTargetKey();
    this.getOrCreateState(key).analyzeResults = v;
  }

  get _currentView(): BaseAnalyzeResult[] {
    const key = this.getActiveTargetKey();
    return this.getOrCreateState(key).currentView;
  }

  set _currentView(v: BaseAnalyzeResult[]) {
    const key = this.getActiveTargetKey();
    this.getOrCreateState(key).currentView = v;
  }

  get _currentFilePath(): string | undefined {
    const key = this.getActiveTargetKey();
    return this.getOrCreateState(key).currentFilePath;
  }

  set _currentFilePath(v: string | undefined) {
    const key = this.getActiveTargetKey();
    this.getOrCreateState(key).currentFilePath = v;
  }

  /**
   * HMRouter-specific properties proxied here to achieve per-target isolation
   * without changing public interfaces used by processors.
   */
  get routerMap(): any[] {
    const key = this.getActiveTargetKey();
    return this.getOrCreateState(key).routerMap;
  }

  set routerMap(v: any[]) {
    const key = this.getActiveTargetKey();
    this.getOrCreateState(key).routerMap = v;
  }

  get generatedPaths(): Map<string, string> {
    const key = this.getActiveTargetKey();
    return this.getOrCreateState(key).generatedPaths;
  }

  set generatedPaths(v: Map<string, string>) {
    const key = this.getActiveTargetKey();
    this.getOrCreateState(key).generatedPaths = v;
  }
}
